// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Jeff Stewart
// Inspects configurations from Outlook Express 6, Outlook 2000,
// Outlook 2003, Outlook 2007, and Windows Mail.

#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QString>
#include <QtCore/QList>
#include <QtXML/QDomDocument>
#include <QtXML/QDomNode>
#include <windows.h>

#include "util/common.h"
#include "util/protocol.h"
#include "util/provider.h"
#include "config/binversion.h"
#include "config/outlook.h"
#include "config/registrykey.h"
#include "config/xmlregistrykey.h"

#ifndef UNICODE
#error THIS CODEBASE ASSUMES A UNICODE BULD
#endif  // UNICODE

namespace popdiag {

typedef scoped_ptr<RegistryKey> KeyHolder;

/*
 * These little classes are just a way of collecting all the constants
 * associated with a particular protocol into an object that can be passed
 * around.
 */

// Configuration faults common to all protocols.
class ProtocolFaults {
 public:
  virtual FaultId Server() const = 0;
  virtual FaultId Port() const = 0;
};

// Faults for receving protocols (POP and IMAP).
class ReceiveProtocolFaults : public ProtocolFaults {
 public:
  virtual FaultId UseSsl() const = 0;
  virtual FaultId UseSpa() const = 0;
  virtual FaultId Username() const = 0;
};

class PopFaults : public ReceiveProtocolFaults {
 public:
  virtual FaultId Server() const { return POP3_SERVER; }
  virtual FaultId Port() const  { return POP3_PORT; }
  virtual FaultId UseSsl() const { return POP3_USE_SSL; }
  virtual FaultId UseSpa() const { return POP3_USE_SPA; }
  virtual FaultId Username() const { return POP3_USERNAME; }
};

class ImapFaults : public ReceiveProtocolFaults {
 public:
  virtual FaultId Server() const { return IMAP_SERVER; }
  virtual FaultId Port() const  { return IMAP_PORT; }
  virtual FaultId UseSsl() const { return IMAP_USE_SSL; }
  virtual FaultId UseSpa() const { return IMAP_USE_SPA; }
  virtual FaultId Username() const { return IMAP_USERNAME; }
};

class SmtpFaults : public ProtocolFaults {
 public:
  virtual FaultId Server() const { return SMTP_SERVER; }
  virtual FaultId Port() const  { return SMTP_PORT_465; }
};

// Registry keys common to all protocols.
class ProtocolRegistryKeys {
 public:
  virtual RegistryEntryId Server() const = 0;
  virtual RegistryEntryId Port() const = 0;
};

class SmtpRegistryKeys : public ProtocolRegistryKeys {
 public:
  virtual RegistryEntryId Server() const { return REG_SMTP_SERVER; }
  virtual RegistryEntryId Port() const  { return REG_SMTP_PORT; }
};

// Registry keys for receving protocols.
class ReceiveProtocolRegistryKeys : public ProtocolRegistryKeys {
 public:
  virtual RegistryEntryId UseSsl() const = 0;
  virtual RegistryEntryId UseSpa() const = 0;
  virtual RegistryEntryId Username() const = 0;
};

class PopRegistryKeys : public ReceiveProtocolRegistryKeys {
 public:
  virtual RegistryEntryId Server() const { return REG_POP3_SERVER; }
  virtual RegistryEntryId Port() const  { return REG_POP3_PORT; }
  virtual RegistryEntryId UseSsl() const { return REG_POP3_USE_SSL; }
  virtual RegistryEntryId UseSpa() const { return REG_POP3_USE_SPA; }
  virtual RegistryEntryId Username() const { return REG_POP3_USERNAME; }
};

class ImapRegistryKeys : public ReceiveProtocolRegistryKeys {
 public:
  virtual RegistryEntryId Server() const { return REG_IMAP_SERVER; }
  virtual RegistryEntryId Port() const  { return REG_IMAP_PORT; }
  virtual RegistryEntryId UseSsl() const { return REG_IMAP_USE_SSL; }
  virtual RegistryEntryId UseSpa() const { return REG_IMAP_USE_SPA; }
  virtual RegistryEntryId Username() const { return REG_IMAP_USERNAME; }
};

Outlook::Outlook(OutlookVersion version) : version_(version) {
}

Outlook::~Outlook() {
}

const char *GetRegistryKeyName(RegistryEntryId id, OutlookVersion version) {
  // Outlook 2000 looks like outlook express in the registry.
  bool uses_oe_regnames = false;
  switch (version) {
  case WINDOWS_MAIL:  // fall through
  case WINDOWS_LIVE_MAIL:  // fall through
  case OUTLOOK_EXPRESS_6:  // fall through
  case OUTLOOK_2000:
    uses_oe_regnames = true;
  }

  switch (id) {
    //--------------------------------------------------------
    // Common:
    //--------------------------------------------------------
  case ACCOUNT_NAME:
    return "Account Name";
  case REG_POP3_PORT:
    return "POP3 Port";
  case REG_POP3_SERVER:
    return "POP3 Server";
  case REG_POP3_SKIP_ACCOUNT:
    return "POP3 Skip Account";

  case REG_IMAP_PORT:
    return "IMAP Port";
  case REG_IMAP_SERVER:
    return "IMAP Server";
  case REG_IMAP_FOLDER_PATH:
    // TODO: This might be different on outlook express
    return "IMAP Folder Path";

  case REG_SMTP_AUTH_METHOD:
    return "SMTP Auth Method";
  case REG_SMTP_PORT:
    return "SMTP Port";
  case REG_SMTP_SSL_VARIANT:
    // yes, we have two names that refer to this.  Outlook 2007 takes this
    // value used in Outlook express and turns it into an enum for
    // plaintext/tls/ssl/autodetect.
    return "SMTP Secure Connection";
  case REG_SMTP_SERVER:
    return "SMTP Server";
  case REG_SMTP_SPLIT_MESSAGES:
    return "SMTP Split Messages";
  case REG_SMTP_USE_AUTH:
    return "SMTP Use Auth";
    //-----------------------------------------------------------
    // Variable:               Outlook Express            Outlook
    //-----------------------------------------------------------
  case REG_POP3_LEAVE_ON_SERVER:
    return (uses_oe_regnames ? "Leave Mail On Server"   : "Leave On Server");
  case REG_POP3_TIMEOUT:
    return (uses_oe_regnames ? "POP3 Timeout"           : "Timeout");
  case REG_POP3_USE_SPA:
    return (uses_oe_regnames ? "POP3 Use Sicily"        : "POP3 Use SPA");
  case REG_POP3_USE_SSL:
    return (uses_oe_regnames ? "POP3 Secure Connection" : "POP3 Use SSL");
  case REG_POP3_USERNAME:
    return (uses_oe_regnames ? "POP3 User Name"         : "POP3 User");
  case REG_IMAP_USE_SSL:
    return (uses_oe_regnames ? "IMAP Secure Connection" : "IMAP Use SSL");
  case REG_IMAP_USE_SPA:
    return (uses_oe_regnames ? "IMAP Use Sicily"        : "IMAP Use SPA");
  case REG_IMAP_USERNAME:
    return (uses_oe_regnames ? "IMAP User Name"         : "IMAP User");
  case REG_SMTP_AUTH_USERNAME:
    return (uses_oe_regnames ? "SMTP User Name"         : "SMTP User");
  case REG_SMTP_DISPLAY_NAME:
    return (uses_oe_regnames ? "SMTP Display Name"      : "Display Name");
  case REG_SMTP_EMAIL_ADDRESS:
    return (uses_oe_regnames ? "SMTP Email Address"     : "Email");
  case REG_SMTP_TIMEOUT:
    return (uses_oe_regnames ? "SMTP Timeout"           : "Timeout");
  case REG_SMTP_USE_SPA:
    return (uses_oe_regnames ? "SMTP Use Sicily"        : "SMTP Use SPA");
  case REG_SMTP_USE_SSL:
    return (uses_oe_regnames ? "SMTP Secure Connection" : "SMTP Use SSL");
  }
  return "Unknown";
}

QString Outlook::program_name() const {
  // I can't just call program_name_in_english here to fill in the first
  // parameter for the tr macro.  The translation tools only extract
  // compile-time literals.  So we have to duplicate the names.
  switch (version_) {
  case OUTLOOK_EXPRESS_6:
    return tr("Microsoft Outlook Express", "Email reader program name");
  case WINDOWS_MAIL:
    return tr("Microsoft Windows Mail", "Email reader program name");
  case WINDOWS_LIVE_MAIL:
    return tr("Microsoft Windows Live Mail", "Email reader program name");
  case OUTLOOK_2000:
    return tr("Microsoft Outlook 2000", "Email reader program name");
  case OUTLOOK_2002:
    return tr("Microsoft Outlook 2002", "Email reader program name");
  case OUTLOOK_2003:
    return tr("Microsoft Outlook 2003", "Email reader program name");
  case OUTLOOK_2007:
    return tr("Microsoft Outlook 2007", "Email reader program name");
  }
  return "Unknown";
}

QString OutlookProfileInspection::program_name_in_english() const {
  // For use in reports.  Not translated.
  switch (version_) {
  case OUTLOOK_EXPRESS_6:
    return "Microsoft Outlook Express";
  case WINDOWS_MAIL:
    return "Microsoft Windows Mail";
  case WINDOWS_LIVE_MAIL:
    return "Microsoft Windows Live Mail";
  case OUTLOOK_2000:
    return "Microsoft Outlook 2000";
  case OUTLOOK_2002:
    return "Microsoft Outlook 2002";
  case OUTLOOK_2003:
    return "Microsoft Outlook 2003";
  case OUTLOOK_2007:
    return "Microsoft Outlook 2007";
  }
  return "Unknown";
}

QString Outlook::program_code() const {
  // For use in hyperlinks to support.  Not translated.
  switch (version_) {
  case OUTLOOK_EXPRESS_6:
    return "e6";
  case WINDOWS_MAIL:
    return "wm";
  case WINDOWS_LIVE_MAIL:
    return "wlm";
  case OUTLOOK_2000:
    return "o9";
  case OUTLOOK_2002:
    return "o2";
  case OUTLOOK_2003:
    return "o3";
  case OUTLOOK_2007:
    return "o7";
  }
  return "un";
}

QString Outlook::GetBinaryPath() const {
  const RegistryKey &rk = RegistryKey::LocalMachine();
  QString key_name;
  QString entry = "Path";
  QString binary = "outlook.exe";
  switch (version_) {
  case OUTLOOK_2000:
    key_name = "Software\\Microsoft\\Office\\9.0\\Common\\InstallRoot";
    break;
  case OUTLOOK_2002:
    key_name = "Software\\Microsoft\\Office\\10.0\\Common\\InstallRoot";
    break;
  case OUTLOOK_2003:
    key_name = "Software\\Microsoft\\Office\\11.0\\Outlook\\InstallRoot";
    break;
    break;
  case OUTLOOK_2007:
    key_name = "Software\\Microsoft\\Office\\12.0\\Outlook\\InstallRoot";
    break;
  case WINDOWS_MAIL:
    key_name = "Software\\Microsoft\\Windows Mail";
    entry = "InstallRoot";
    binary = "WinMail.exe";
    break;
  case WINDOWS_LIVE_MAIL:
    key_name = "Software\\Microsoft\\Windows Live Mail";
    entry = "InstallRoot";
    binary = "wlmail.exe";
    break;
  case OUTLOOK_EXPRESS_6:
    key_name = "Software\\Microsoft\\Outlook Express";
    entry = "InstallRoot";
    binary = "msimn.exe";
    break;
  }
  KeyHolder iroot(rk.ChildKey(key_name));
  if (iroot != NULL) {
    QString base_path;
    if (iroot->ReadStringEntry(entry, &base_path)) {
      return QDir(base_path).filePath(binary);
    }
  }
  return "";
}

bool Outlook::CheckConfiguration(VerificationSet *out) const {
  // Our job here is to find the potentially interesting profiles and populate
  // out with them.  Iff we find any, return true.
  QString binary_path = GetBinaryPath();
  if (binary_path.isEmpty() || !QFile::exists(binary_path)) {
    // Binary isn't present where we expect it.  Any configurations in the
    // registry were left behind after a botched uninstall, probably.
    // At any rate they aren't usable, so don't advertise them.
    return false;
  }
  QList<RegistryKey *> interesting_profiles;
  GetInterestingProfiles(&interesting_profiles);
  for (QList<RegistryKey *>::iterator i = interesting_profiles.begin();
       i != interesting_profiles.end(); ++i) {
    OutlookProfileInspection *inspector =
      new OutlookProfileInspection(**i, version_);

    // Populate the profile with initial values.  The UI code will recall
    // Recalculate later with the right email address, but we need to get some
    // other values populated now so the pulldown displays the right data.
    inspector->Recalculate("");

    out->push_back(inspector);
    delete *i;
  }
  return true;
}

RegistryKey *Outlook::GetProfileContainer() const {
  KeyHolder profiles;
  switch (version_) {
  case OUTLOOK_2000:
    return RegistryKey::CurrentUser().ChildKey(
          "Software\\Microsoft\\Office\\Outlook\\"
          "OMI Account Manager\\Accounts");
  case OUTLOOK_2002:  // fall through.
  case OUTLOOK_2003:  // fall through.
  case OUTLOOK_2007:
    {
      profiles.reset(RegistryKey::CurrentUser().ChildKey(
          "Software\\Microsoft\\Windows NT\\CurrentVersion\\"
          "Windows Messaging Subsystem\\Profiles"));
      if (profiles == NULL) {
        return NULL;
      }

      // Find the name of the default profile.
      QString profile_name;
      if (!profiles->ReadStringEntry("DefaultProfile", &profile_name)) {
        return NULL;
      }

      // Fetch that profile container.
      KeyHolder default_profile(profiles->ChildKey(profile_name));
      if (default_profile == NULL) {
        return NULL;
      }

      // In that container, look at this fixed GUID for the entry that contains
      // the list of configured profiles.
      return default_profile->ChildKey("9375CFF0413111d3B88A00104B2A6676");
    }
  case OUTLOOK_EXPRESS_6:
    return RegistryKey::CurrentUser().ChildKey(
        "Software\\Microsoft\\Internet Account Manager\\Accounts");
  case WINDOWS_MAIL:
  case WINDOWS_LIVE_MAIL:
    // Doesn't use this code path.  Gets profiles from disk files.
    CHECK(false);
    break;
  }
  // TODO: Populate Outlook 2002 container
  return NULL;
}

bool Outlook::GetInterestingProfiles(QList<RegistryKey *> *out) const {
  switch (version_) {
    case WINDOWS_MAIL:
    case WINDOWS_LIVE_MAIL:
      return GetInterestingProfilesFromDisk(out);
    default:
      return GetInterestingProfilesFromRegistry(out);
  }
}

bool Outlook::GetInterestingProfilesFromDisk(QList<RegistryKey *> *out) const {
  // Get the disk root from the registry:
  QString registry_path;
  switch (version_) {
    case WINDOWS_MAIL:
      registry_path = "Software\\Microsoft\\Windows Mail";
      break;
    case WINDOWS_LIVE_MAIL:
      registry_path = "Software\\Microsoft\\Windows Live Mail";
      break;
    default:
      CHECK(false);
  }
  KeyHolder base(RegistryKey::CurrentUser().ChildKey(registry_path));
  if (base == NULL) {
    return false;
  }
  QString store_path;
  if (!base->ReadStringEntry("Store Root", &store_path)) {
    return false;
  }

  QDir store_path_dir(store_path);
  if (!store_path_dir.exists() || !store_path_dir.isReadable()) {
    return false;
  }

  // The directory we want is called "Local Folders" in English, but let's find
  // all *.oeaccount files one level down from the base to insulate us from
  // localization effects.
  bool found = false;
  QStringList child_dirs = store_path_dir.entryList(QDir::Dirs |
                                                    QDir::NoDotAndDotDot |
                                                    QDir::Executable);
  for (QStringList::iterator i = child_dirs.begin();
       i != child_dirs.end(); ++i) {
    QDir child_dir(store_path_dir.filePath(*i));
    QStringList filters;
    filters.append("*.oeaccount");
    QStringList profiles = child_dir.entryList(filters, QDir::Files |
                                                        QDir::Readable);
    for (QStringList::iterator profile = profiles.begin();
         profile != profiles.end(); ++profile) {
      QString path = child_dir.filePath(*profile);
      KeyHolder key(new XmlRegistryKey(path));
      QString user_name;
      if (key->ReadStringEntry(GetRegistryKeyName(REG_POP3_USERNAME, version_),
                               &user_name) ||
          key->ReadStringEntry(GetRegistryKeyName(REG_IMAP_USERNAME, version_),
                               &user_name)) {
        found = true;
        out->append(key.release());
      }
    }
  }
  return found;
}

bool Outlook::GetInterestingProfilesFromRegistry(
    QList<RegistryKey *> *out) const {
  KeyHolder profile_container(GetProfileContainer());
  if (profile_container == NULL) {
    return false;
  }
  QList<QString> children;
  profile_container->GetChildKeyNames(&children);

  bool found = false;
  for (QList<QString>::const_iterator i = children.begin();
       i != children.end(); ++i) {
    KeyHolder profile(profile_container->ChildKey(*i));
    if (profile == NULL) {
      continue;
    }

    QString server;
    if (profile->ReadBinaryString(
            GetRegistryKeyName(REG_POP3_SERVER, version_), &server) ||
        profile->ReadBinaryString(
            GetRegistryKeyName(REG_IMAP_SERVER, version_), &server)) {
      found = true;
      out->push_back(profile.release());
    }
  }
  return found;
}

OutlookProfileInspection::OutlookProfileInspection(
  const RegistryKey &root, OutlookVersion version)
    : root_(root.Copy()), version_(version) {
}

void OutlookProfileInspection::NoteVersion() {
  QString binary_path = Outlook(version_).GetBinaryPath();
  if (binary_path.isEmpty()) {
    LogDetails("Binary Path", false,
               "Cannot determine path to mail reader binary");
    return;
  }

  if (!QFile::exists(binary_path)) {
    LogDetails("Binary Path", false,
               QString("Binary not found where expected: %1")
               .arg(binary_path));
    return;
  }
  LogDetails("Binary Path", true, binary_path);

  QString description;
  bool version_read = BinVersion::Read(binary_path, &description);
  LogDetails("Binary Version", version_read, description);
}

void OutlookProfileInspection::Recalculate(const QString &email_address) {
  // Note that email_address will be empty the first time this method is
  // called.  Then we will be recalled by the UI code with the value
  // provided by the user in the login page.
  faults_.clear();
  root_->Refresh();
  ClearLog();
  StartLogDetails();
  email_ = email_address;
  LogDetails("Program", true, program_name_in_english());
  NoteVersion();

  Outlook src(version_);
  set_program_name(src.program_name());
  set_program_code(src.program_code());
  QString account_name;
  ReadConfig(ACCOUNT_NAME, &account_name);
  LogDetails("Account Name", true, account_name);
  set_profile_name(account_name);

  ValidateSmtp();
  if (HasPop()) {
    ValidatePop();
  }
  if (HasImap()) {
    set_display_protocol("IMAP");
    ValidateImap();
  }

  if (faults_.isEmpty()) {
    LogDetails("Overall", true, "No faults found");
  } else {
    QString faults;
    for (FaultSet::iterator i = faults_.begin(); i != faults_.end(); ++i) {
      faults += QString("%1 ").arg(i->first);
    }
    LogDetails("Overall", false, "Fault codes found: " + faults);
  }

  QString basename;
  switch (version_) {
    case WINDOWS_MAIL:
      basename = "winmail.exe";
      break;
    case WINDOWS_LIVE_MAIL:
      basename = "wlmail.exe";
      break;
    case OUTLOOK_EXPRESS_6:
      basename = "msimn.exe";
      break;
    default:
      basename = "outlook.exe";
  }
  if (ConfigVerifierRegistry::GetDefaultMailProgramBaseName().compare(
      basename, Qt::CaseInsensitive) == 0) {
    set_is_default_program(true);
  }
  EndLogDetails();
}

bool OutlookProfileInspection::HasServer(const RegistryEntryId& reg_key) const {
  QString server;
  return ReadConfig(reg_key, &server);
}

bool OutlookProfileInspection::HasImap() const {
  return HasServer(REG_IMAP_SERVER);
}

bool OutlookProfileInspection::HasPop() const {
  return HasServer(REG_POP3_SERVER);
}

void OutlookProfileInspection::ValidatePop() {
  ValidateServerAndPort(Protocol::POP(),
                        PopRegistryKeys(),
                        PopFaults(),
                        &pop_server_,
                        NULL,
                        true);
  ValidateAuth(Protocol::POP(),
               PopRegistryKeys(),
               PopFaults());
  ValidatePopMisc();
}

// Get a string value from the registry
bool OutlookProfileInspection::ReadConfig(RegistryEntryId item,
                                          QString *out) const {
  return root_->ReadBinaryString(GetRegistryKeyName(item, version_), out);
}

// Get an integral value from the registry
bool OutlookProfileInspection::ReadConfig(RegistryEntryId item,
                                          int *out) const {
  return root_->ReadIntEntry(GetRegistryKeyName(item, version_), out);
}

void OutlookProfileInspection::ValidateServerAndPort(
    const Protocol& protocol,
    const ProtocolRegistryKeys& registry_keys,
    const ProtocolFaults& faults,
    QString *serverptr,
    int *portptr,
    bool validate_port) {
  QString server_pretty_name(protocol.Name() + " Server");

  QString server;
  if (!ReadConfig(registry_keys.Server(), &server)) {
    LogDetails(server_pretty_name, false, "Cannot Read");
    LogDetails("Registry values", false, root_->Dump());
    faults_.push_back(Fault(PROFILE_NOT_UNDERSTOOD, ""));
  } else {
    if (!protocol.IsOurMailServer(server)) {
      LogDetails(server_pretty_name, false, server);
      faults_.push_back(Fault(faults.Server(), server));
    } else {
      LogDetails(server_pretty_name, true, server);
    }
  }
  if (serverptr != NULL)
    *serverptr = server;
  int port;
  if (!ReadConfig(registry_keys.Port(), &port)) {
    port = protocol.PlaintextPort();
  }
  LogDetails(protocol.Name() + " Port", port == protocol.SecurePort(),
             QString("%1").arg(port));

  if (port != protocol.SecurePort() && validate_port) {
    faults_.push_back(Fault(faults.Port(), IntToString(port)));
  }
  if (portptr != NULL)
    *portptr = port;
}

void OutlookProfileInspection::ValidateAuth(
    const Protocol& protocol,
    const ReceiveProtocolRegistryKeys& registry_keys,
    const ReceiveProtocolFaults& faults) {
  int use_ssl;
  if (!ReadConfig(registry_keys.UseSsl(), &use_ssl)) {
    use_ssl = 0;
  }
  LogDetails(protocol.Name() + " Use SSL", use_ssl != 0, "");
  if (use_ssl != 1) {
    faults_.push_back(Fault(faults.UseSsl(), ""));
  }

  int spa = 0;
  if (ReadConfig(registry_keys.UseSpa(), &spa)) {
    if (spa != 0 && protocol.MustNotUseSpa())
      faults_.push_back(Fault(faults.UseSpa(), ""));
  }
  LogDetails(protocol.Name() + " Use SPA", spa == 0, "");

  QString user;
  if (!ReadConfig(registry_keys.Username(), &user)) {
    LogDetails(protocol.Name() + " Username", false, "None stored");
    faults_.push_back(Fault(faults.Username(), ""));
  } else {
    LogDetails(protocol.Name() + " Username",
               user.compare(email_, Qt::CaseInsensitive) == 0,
               user);
    if (!protocol.VerifyUsernameFromEmailAddress(user, email_)) {
      faults_.push_back(Fault(faults.Username(), user));
    }
  }
}

void OutlookProfileInspection::ValidatePopMisc() {
  int pop_leave_on_server = 0;
  if (ReadConfig(REG_POP3_LEAVE_ON_SERVER, &pop_leave_on_server)) {
    if (pop_leave_on_server != 0 && provider->PopMustNotLeaveOnServer()) {
      faults_.push_back(Fault(POP3_LEAVE_ON_SERVER, ""));
    }
  }
  LogDetails("POP Leave on Server", pop_leave_on_server == 0, "");

  int timeout_in_seconds = 60;
  if (ReadConfig(REG_POP3_TIMEOUT, &timeout_in_seconds)) {
    if (timeout_in_seconds < 60) {
      faults_.push_back(Fault(POP3_TIMEOUT, QString("%1")
                              .arg(timeout_in_seconds)));
    }
  }
  LogDetails("POP Timeout", timeout_in_seconds >= 60, QString("%1")
             .arg(timeout_in_seconds));

  int skip_account = 0;
  if (ReadConfig(REG_POP3_SKIP_ACCOUNT, &skip_account)) {
    if (skip_account != 0) {
      faults_.push_back(Fault(POP3_ACCOUNT_SKIPPED, ""));
      LogDetails("POP3 Account Skipped", false, "account is not polled");
    }
  }
}

void OutlookProfileInspection::ValidateImap() {
  ValidateServerAndPort(Protocol::IMAP(),
                        ImapRegistryKeys(),
                        ImapFaults(),
                        &imap_server_,
                        NULL,
                        true);
  ValidateAuth(Protocol::IMAP(),
               ImapRegistryKeys(),
               ImapFaults());
  ValidateImapMisc();
}

void OutlookProfileInspection::ValidateImapMisc() {
  QString folder_path;
  ReadConfig(REG_IMAP_FOLDER_PATH, &folder_path);
  bool ok = (folder_path == "");
  if (!ok) {
    faults_.push_back(Fault(IMAP_FOLDER_PATH, folder_path));
  }
  LogDetails("IMAP Folder Path", ok, folder_path);
}


void OutlookProfileInspection::ValidateSmtp() {
  ValidateServerAndPort(Protocol::SMTP(),
                        SmtpRegistryKeys(),
                        SmtpFaults(),
                        &smtp_server_,
                        &smtp_port_,
                        !provider->SmtpCanUsePlaintextPort());
  ValidateSmtpAuth();
  ValidateSmtpMisc();
}

void OutlookProfileInspection::ValidateSmtpAuth() {
  QString email;
  if (!ReadConfig(REG_SMTP_EMAIL_ADDRESS, &email)) {
    LogDetails("SMTP Email Address", false, "not stored");
    faults_.push_back(Fault(SMTP_EMAIL_ADDRESS, ""));
  } else {
    set_display_email(email);
    email_ = email;
    LogDetails("SMTP Email Address", true, email);
  }

  if (version_ == OUTLOOK_2007) {
    ValidateSmtpOutlook2007Ssl();
    // Do the tests below as well.
  }

  int smtp_use_ssl = 0;
  if (!ReadConfig(REG_SMTP_USE_SSL, &smtp_use_ssl)) {
    smtp_use_ssl = 0;
  }
  LogDetails("SMTP Use SSL", smtp_use_ssl == 1, "");
  if (smtp_use_ssl != 1) {
    faults_.push_back(Fault(SMTP_USE_SSL, ""));
  }

  if (version_ == WINDOWS_MAIL || version_ == WINDOWS_LIVE_MAIL ||
      version_ == OUTLOOK_EXPRESS_6 || version_ == OUTLOOK_2000) {
    // Outlook 2000's registry entries look like outlook express.
    ValidateSmtpAuthOutlookExpress();
  } else {
    ValidateSmtpAuthOutlook();
  }
}

void OutlookProfileInspection::ValidateSmtpOutlook2007Ssl() {
  // Outlook 2007 finally supports TLS in addition to SSL  So port validation
  // has more possibly correct (and incorrect) configurations.  We're going
  // to steer people to SSL on 465.  The port is checked elsewhere.  Just
  // check the SSL variant here.
  int smtp_ssl_variant = 0;
  if (ReadConfig(REG_SMTP_SSL_VARIANT, &smtp_ssl_variant)) {
    enum { PLAINTEXT = 0, SSL = 1, TLS = 2, AUTO = 3 };
    const char *name = "unknown";
    switch (smtp_ssl_variant) {
    case PLAINTEXT:
      name = "plaintext";
      break;
    case SSL:
      name = "ssl";
      break;
    case TLS:
      name = "tls";
      break;
    case AUTO:
      name = "auto";
      break;
    }
    LogDetails("SMTP SSL Variant", smtp_ssl_variant == SSL, name);
    if (smtp_ssl_variant != SSL) {
      faults_.push_back(Fault(SMTP_USE_SSL, ""));
    }
  }
}

void OutlookProfileInspection::ValidateSmtpAuthOutlook() {
  int smtp_use_auth;
  if (!ReadConfig(REG_SMTP_USE_AUTH, &smtp_use_auth)) {
    smtp_use_auth = 0;
  }
  LogDetails("SMTP Use Auth", smtp_use_auth != 0, "");
  if (smtp_use_auth != 1) {
    faults_.push_back(Fault(SMTP_USE_AUTH, ""));
  }

  int smtp_auth_method;
  if (!ReadConfig(REG_SMTP_AUTH_METHOD, &smtp_auth_method)) {
    smtp_auth_method = 0;
  }

  enum OutlookAuthMethods { USE_POP_AUTH_SETTINGS = 0,
                            USE_PROVIDED_CREDENTIALS = 1,
                            USE_POP_BEFORE_SMTP = 2 };

  if (smtp_auth_method == USE_POP_AUTH_SETTINGS) {
    LogDetails("SMTP Auth Method", true, "Same as POP");
  } else if (smtp_auth_method == USE_POP_BEFORE_SMTP) {
    LogDetails("SMTP Auth Method", false, "POP before MSA not supported");
    faults_.push_back(Fault(SMTP_AUTH_POP_BEFORE_SMTP, ""));
  } else if (smtp_auth_method == USE_PROVIDED_CREDENTIALS) {
    LogDetails("SMTP Auth Method", true, "Explicitly set");

    // Only in this case do we check SPA for SMTP.  Outlook ignores the
    // value in the registry unless the method is USE_PROVIDED_CREDENTIALS,
    // and it won't necessarily update it to match the prefs UI unless that
    // is the case.
    int smtp_use_spa = 0;
    if (ReadConfig(REG_SMTP_USE_SPA, &smtp_use_spa)) {
      if (smtp_use_spa != 0) {
        faults_.push_back(Fault(SMTP_USE_SPA, ""));
      }
      LogDetails("SMTP Use SPA", smtp_use_spa == 0, "");
    }

    // Make sure the right username is used.
    QString smtp_user;
    if (ReadConfig(REG_SMTP_AUTH_USERNAME, &smtp_user)) {
      if (smtp_user.compare(email_, Qt::CaseInsensitive) != 0) {
        LogDetails("SMTP Username", false, "Username wrong: " + smtp_user);
        faults_.push_back(Fault(SMTP_USERNAME, smtp_user));
      } else {
        LogDetails("SMTP Username", true, smtp_user);
      }
    }
  }
}


void OutlookProfileInspection::ValidateSmtpAuthOutlookExpress() {
  int smtp_use_spa = 0;
  // Yes, the value "SMTP Use Sicily" is not a boolean for outlook express.
  if (!ReadConfig(REG_SMTP_USE_SPA, &smtp_use_spa)) {
    return;
  }
  enum OutlookAuthMethods { NO_AUTH = 0,
                            USE_SPA = 1,
                            USE_POP_AUTH_SETTINGS = 2,
                            USE_PROVIDED_CREDENTIALS = 3 };
  switch (smtp_use_spa) {
  case NO_AUTH:
    faults_.push_back(Fault(SMTP_USE_AUTH, ""));
    LogDetails("SMTP Use Auth", false, "No auth selected");
    break;
  case USE_SPA:
    LogDetails("SMTP Use Auth", false, "SPA selected");
    faults_.push_back(Fault(SMTP_USE_SPA, ""));
    break;
  case USE_POP_AUTH_SETTINGS:
    LogDetails("SMTP Use Auth", true, "Same as POP");
    // Use POP auth settings.  good.
    break;
  case USE_PROVIDED_CREDENTIALS:
    {
      LogDetails("SMTP Use Auth", true, "Explicitly set");
      QString smtp_user;
      if (ReadConfig(REG_SMTP_AUTH_USERNAME, &smtp_user)) {
        if (smtp_user.compare(email_, Qt::CaseInsensitive) != 0) {
          LogDetails("SMTP Username", false, "Username wrong: " + smtp_user);
          faults_.push_back(Fault(SMTP_USERNAME, smtp_user));
        } else {
          LogDetails("SMTP Username", true, smtp_user);
        }
      }
    }
    break;
  default:
    LogDetails("Profile parse", false, root_->Dump());
    faults_.push_back(Fault(PROFILE_NOT_UNDERSTOOD, ""));
    break;
  }
}

void OutlookProfileInspection::ValidateSmtpMisc() {
  QString display_name;
  if (ReadConfig(REG_SMTP_DISPLAY_NAME, &display_name)) {
    set_display_name(display_name);
  }
  LogDetails("Display Name", true, display_name);

  int timeout_in_seconds = 60;
  if (ReadConfig(REG_SMTP_TIMEOUT, &timeout_in_seconds)) {
    if (timeout_in_seconds < 60) {
      faults_.push_back(Fault(SMTP_TIMEOUT, QString("%1")
                              .arg(timeout_in_seconds)));
    }
  }
  LogDetails("SMTP Timeout", timeout_in_seconds >= 60, QString("%1")
             .arg(timeout_in_seconds));

  int split_messages = 0;
  if (ReadConfig(REG_SMTP_SPLIT_MESSAGES, &split_messages)) {
    if (split_messages != 0) {
      faults_.push_back(Fault(SMTP_SPLIT_MESSAGES, ""));
      LogDetails("SMTP Split Messages", false, "Messages will be split");
    }
  }
}

}  // namespace popdiag
